package model;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import model.exceptions.AlreadyCalculatedBelegungenException;
import model.exceptions.NotEnoughFreeRoomsException;

import controller.PersistenceManager;

@Entity
public class Reservierung implements IEntity
{
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;
	@OneToMany(mappedBy = "reservierung", cascade = CascadeType.ALL)
	private List<Belegung> belegungen = new ArrayList<Belegung>();
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date von;
	@Temporal(TemporalType.TIMESTAMP)
	private Date bis;
	@Temporal(TemporalType.TIMESTAMP)
	private Date erstellt = new Date();
	@OneToOne(cascade = CascadeType.PERSIST)
	private Rechnung rechnung;

	public Rechnung getRechnung()
	{
		return rechnung;
	}

	@Transient
	private List<Kunde> tempKunden = new ArrayList<Kunde>();

	public Reservierung(List<Belegung> belegungen, Date von, Date bis, Date erstellt, Rechnung rechnung)
	{
		super();
		this.belegungen = belegungen;
		for (Belegung b : belegungen)
			b.setReservierung(this);
		this.von = von;
		this.bis = bis;
		this.erstellt = erstellt;
		this.rechnung = rechnung;
	}

	public Reservierung(List<Belegung> belegungen, Date von, Date bis)
	{
		this(belegungen, von, bis, new Date(), null);
	}

	public Reservierung()
	{
	}

	// ##################################################################
	// ======================== Business Logic ==========================

	private boolean alreadyCalculated()
	{
		if (this.belegungen.size() > 0)
			return true;
		return false;
	}

	public int countChildren()
	{
		int children = 0;
		if (this.alreadyCalculated())
		{
			for (Belegung b : this.getBelegungen())
				for (Kunde k : b.getKunden())
					if (k.isChild())
						children++;
		} else
		{
			for (Kunde k : this.tempKunden)
				if (k.isChild())
					children++;
		}
		return children;
	}

	public int countAdults()
	{
		int adults = 0;
		if (this.alreadyCalculated())
		{
			for (Belegung b : this.getBelegungen())
				for (Kunde k : b.getKunden())
					if (k.isAdult())
						adults++;
		} else
		{
			for (Kunde k : this.tempKunden)
				if (k.isAdult())
					adults++;
		}
		return adults;
	}

	/**
	 * Adds a Kunde to this Reservierung
	 */
	public void addKunde(Kunde k)
	{
		this.tempKunden.add(k);
	}

	/**
	 * Calculates a List of possible RoomCombinations for this Reservierung
	 * 
	 * @param possibleRooms
	 * @return
	 * @throws NotEnoughFreeRoomsException
	 *             if not enough Rooms are avaiable in the specified time
	 * @throws AlreadyCalculatedBelegungenException
	 *             if calculation was already sucessfully done
	 */
	public List<List<Zimmer>> doRoomCalculation(List<Zimmer> possibleRooms) throws NotEnoughFreeRoomsException, AlreadyCalculatedBelegungenException
	{
		if (this.alreadyCalculated())
			throw new AlreadyCalculatedBelegungenException();

		List<List<Zimmer>> roomResults = new ArrayList<List<Zimmer>>();
		int children = this.countChildren();
		int adults = this.countAdults();

		// Did we found any free room?
		if (possibleRooms.size() > 0)
		{
			// Sort the rooms after size, biggest first
			Collections.sort(possibleRooms, new Comparator<Zimmer>()
			{
				@Override
				public int compare(Zimmer o1, Zimmer o2)
				{
					return o2.getMaxBelegung() - o1.getMaxBelegung();
				}
			});

			// Find rooms or combinations of rooms
			for (int i = 0; i < possibleRooms.size(); i++)
			{
				List<Zimmer> roomCombo = new ArrayList<Zimmer>();
				int missingBelegungen = adults + children - possibleRooms.get(i).getMaxBelegung();
				roomCombo.add(possibleRooms.get(i));
				List<Zimmer> otherRooms = new ArrayList<Zimmer>(possibleRooms);

				otherRooms.remove(possibleRooms.get(i));
				// as long as we still have persons without a room and still rooms available,
				// keep on adding rooms the room combination
				while (missingBelegungen > 0 && otherRooms.size() > 0)
				{
					List<Zimmer> bigEnough = findBigEnoughRooms(otherRooms, missingBelegungen);
					// If there is one or more rooms big enough, add the smallest
					if (bigEnough.size() > 0)
					{
						roomCombo.add(bigEnough.get(bigEnough.size() - 1));
						missingBelegungen = missingBelegungen - bigEnough.get(bigEnough.size() - 1).getMaxBelegung();
						// Otherwise add the biggest room
					} else
					{
						roomCombo.add(otherRooms.get(0));
						missingBelegungen = missingBelegungen - otherRooms.get(0).getMaxBelegung();
						if (otherRooms.size() >= 1)
						{
							otherRooms = otherRooms.subList(1, otherRooms.size() - 1);
						} else
						{
							otherRooms = otherRooms.subList(0, otherRooms.size() - 1);
						}
					}
				}
				// if we have a proper room combination add it to the result
				if (missingBelegungen <= 0)
				{
					roomResults.add(roomCombo);
				}
			}
		}
		return roomResults;
	}

	private List<Zimmer> findBigEnoughRooms(List<Zimmer> avalibleRooms, int persons)
	{
		List<Zimmer> bigEnoughRooms = new ArrayList<Zimmer>();
		for (Zimmer room : avalibleRooms)
		{
			if (room.getMaxBelegung() >= persons)
			{
				bigEnoughRooms.add(room);
			}
		}
		return bigEnoughRooms;
	}

	/**
	 * Does a freeRoomCalculation and if found, it just assigns the first possible combination to this reservation
	 * 
	 * @param avaiableRooms
	 * @throws AlreadyCalculatedBelegungenException
	 * @throws NotEnoughFreeRoomsException
	 */
	public void doSimpleAssignRoom(List<Zimmer> possibleRooms) throws NotEnoughFreeRoomsException, AlreadyCalculatedBelegungenException
	{	
		ArrayList<Belegung> tmpBelegungen = new ArrayList<Belegung>();
		int addedtoRoom = 0;
		int roomIndex = 0;
		Belegung currentBelegung = new Belegung();
		currentBelegung.setZimmer(possibleRooms.get(roomIndex));
		tmpBelegungen.add(currentBelegung);
		for(int i = 0; i < tempKunden.size();i++){
			if(addedtoRoom == currentBelegung.getZimmer().getMaxBelegung()){
				addedtoRoom = 0;
				roomIndex++;
				currentBelegung = new Belegung();
				currentBelegung.setZimmer(possibleRooms.get(roomIndex));
				tmpBelegungen.add(currentBelegung);
			}
			currentBelegung.addKunde(tempKunden.get(i));
			addedtoRoom++;
		}
		System.out.println("tmpbelegungen size: " + tmpBelegungen.size() + "possible rooms: " + possibleRooms.size());
		setBelegungen(tmpBelegungen);
	}

	public void addBelegung(Belegung b)
	{
		this.belegungen.add(b);
		b.setReservierung(this);
	}

	public void setRechnung(Rechnung r)
	{
		this.rechnung = r;
		this.rechnung.setReservierung(this);
	}
	
	public void clearRechnung(){
		if(rechnung != null){
			rechnung.setReservierung(null);
			rechnung = null;
		}
	}

	public static List<Reservierung> getRerservierungen()
	{
		PersistenceManager pm = PersistenceManager.getInstance();
		return pm.loadAllEntities(new Reservierung());
	}

	// ##################################################################
	// ================== Setters and getters start =====================

	public List<Belegung> getBelegungen()
	{
		return belegungen;
	}

	public double getRabatt()
	{
		double retVal = 0;
		try{
			retVal = rechnung.getPayingCustomer().getRabat();
		}catch(Exception e){ 
			// okay no rechnung set, use 0% 
		}
		
		return retVal;
	}

	public Date getVon()
	{
		return von;
	}

	public void setVon(Date von)
	{
		this.von = von;
	}

	public Date getBis()
	{
		return bis;
	}

	public void setBis(Date bis)
	{
		this.bis = bis;
	}

	public Date getErstellt()
	{
		return erstellt;
	}

	public void setErstellt(Date erstellt)
	{
		this.erstellt = erstellt;
	}

	public void setBelegungen(List<Belegung> belegungen)
	{
		this.belegungen.clear();
		for(Belegung b: belegungen){
			// set proper cross references!
			addBelegung(b);
		}
		
	}

}
